package com.spring.mini.framework.servlet.v3;

import com.spring.mini.framework.annotation.*;
import org.objectweb.asm.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Tinu 1810989@qq.com
 */
public class MyDispatcherServlet extends HttpServlet {

    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();


    //保存url和Method的对应关系
    //private Map<String,Method> handlerMapping = new HashMap<>();

    //保存扫描的所有类名
    private List<String> classNames = new ArrayList<>();

    //ioc容器
    private Map<String,Object> ioc = new ConcurrentHashMap<>();

    public List<Handler> handlerMapping = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 " + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        Handler handler = getHandler(req);

        if (handler==null) {
            resp.getWriter().write("404");
            return;
        }
        //获得方法的形参列表
        Class<?> [] paramTypes = handler.getParamsTypes();

        Object [] paramValues = new Object[paramTypes.length];

        Map<String,String[]> params = req.getParameterMap();

        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            if (!handler.paramIndexMapping.containsKey(entry.getKey())) continue;
            int index = handler.paramIndexMapping.get(entry.getKey());
            paramValues[index] = convert(paramTypes[index],entry.getValue());

        }

        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

        Object returnValue = handler.method.invoke(handler.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){ return; }
        resp.getWriter().write(returnValue.toString());
    }

    private Handler getHandler(HttpServletRequest req) {
        if (this.handlerMapping.isEmpty()) return null;
        String uri = req.getRequestURI();
        String path = req.getContextPath();
        uri = uri.replace(path,"").replaceAll("/+","/");

        for (Handler handler : this.handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(uri);
            if(!matcher.matches()){ continue;}
            return handler;
        }

        return null;
    }

    private Object convert(Class<?> type,String[] values){
        String value = Arrays.toString(values).replaceAll("\\[|\\]","");
        //System.out.println(type.getClass());
        //如果是int
        if(Integer.class == type || type.getName().equals("int")){
            return Integer.valueOf(value);
        }
        else if(Double.class == type){
            return Double.valueOf(value);
        }
        //如果还有double或者其他类型，继续加if
        //这时候，我们应该想到策略模式了
        //在这里暂时不实现，希望小伙伴自己来实现
        return value;
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3、初始化扫描到的类，并且将它们放入到ICO容器之中
        doInstance();

        //4、完成依赖注入
        doAutowired();

        //5、初始化HandlerMapping
        initHandlerMapping();

    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()) return;
        MyRequestMapping requestMapping = null;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)) continue;
            requestMapping = clazz.getAnnotation(MyRequestMapping.class);
            String baseUri = requestMapping.value();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if(!method.isAnnotationPresent(MyRequestMapping.class)) continue;
                requestMapping = method.getAnnotation(MyRequestMapping.class);
                String uri = ("/" + baseUri + "/" + requestMapping.value()).replaceAll("/+","/");
                Pattern pattern = Pattern.compile(uri);
                this.handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                //handlerMapping.put(uri,method);
                System.out.println("Mapped :" + uri + "," + method);
            }

        }

    }

    private void doAutowired() {
        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            if (!entry.getValue().getClass().isAnnotationPresent(MyController.class)) continue;
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(MyAutowired.class)) continue;
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = toLowerFirstCase(field.getType().getSimpleName());
                field.setAccessible(true);
                //用反射机制，动态给字段赋值
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }


    }

    private void doInstance() {
        if (classNames == null) return;
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    Object instance = clazz.newInstance();
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName,instance);
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    MyService service = clazz.getAnnotation(MyService.class);
                    String beanName = service.value();
                    //默认service首字母小写
                    if ("".equals(beanName)) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);

                    for (Class<?> clazzInterface : clazz.getInterfaces()) {
                        if (ioc.containsKey(clazzInterface.getName())) {
                            throw new Exception("The " +clazzInterface.getName()+ " is exists");
                        }
                        ioc.put(toLowerFirstCase(clazzInterface.getSimpleName()),instance);
                    }

                } else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

    //首字母转小写
    private static String toLowerFirstCase(String str) {
        if(Character.isLowerCase(str.charAt(0))) {
            return str;
        } else {
            return Character.toLowerCase(str.charAt(0)) + str.substring(1);
        }
    }



    private void doLoadConfig(String contextConfigLocation) {
        contextConfigLocation = contextConfigLocation.replaceFirst("classpath*:","");
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));
        File classDir = new File(url.getFile());
        // 循环包下文件
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage+"."+file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {continue;}
                String className = scanPackage + "." + file.getName().replace(".class","");
                classNames.add(className);
            }
        }


    }

    /** 使用字节码工具ASM来获取方法的参数名 */
    public static String[] getMethodParamNames(final Method method) throws IOException {

        String methodName = method.getName();
        Class<?>[] methodParameterTypes = method.getParameterTypes();
        int methodParameterCount = methodParameterTypes.length;
        String className = method.getDeclaringClass().getName();
        boolean isStatic = Modifier.isStatic(method.getModifiers());
        String[] methodParametersNames = new String[methodParameterCount];
        InputStream is = MyDispatcherServlet.class.getClassLoader().getResourceAsStream(className.replaceAll("\\.","/")+".class");

        ClassReader cr = new ClassReader(is);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cr.accept(new ClassAdapter(cw) {
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

                final Type[] argTypes = Type.getArgumentTypes(desc);

                //参数类型不一致
                if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {
                    return mv;
                }
                return new MethodAdapter(mv) {
                    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                        //如果是静态方法，第一个参数就是方法参数，非静态方法，则第一个参数是 this ,然后才是方法的参数
                        int methodParameterIndex = isStatic ? index : index - 1;
                        if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
                            methodParametersNames[methodParameterIndex] = name;
                        }
                        super.visitLocalVariable(name, desc, signature, start, end, index);
                    }
                };
            }
        }, 0);
        if (is!=null) {
            is.close();
        }
        return methodParametersNames;
    }

    /**
     * 比较参数是否一致
     */
    private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {
        if (types.length != parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (!Type.getType(parameterTypes[i]).equals(types[i])) {
                return false;
            }
        }
        return true;
    }

    public class Handler {


        private Pattern pattern;

        private Method method;

        private  Object controller;

        private Class<?>[] paramsTypes;

        public Pattern getPattern() {
            return pattern;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class<?>[] getParamsTypes() {
            return paramsTypes;
        }

        //形参列表
        //参数的名字作为key,参数的顺序，位置作为值
        private Map<String,Integer> paramIndexMapping;

        public Handler(Pattern pattern, Object controller, Method method) {
            this.pattern = pattern;
            this.method = method;
            this.controller = controller;

            this.paramsTypes = method.getParameterTypes();

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method){

            //提取方法中加了注解的参数
            //把方法上的注解拿到，得到的是一个二维数组
            //因为一个参数可以有多个注解，而一个方法又有多个参数
            try {
                String[] paramNames = getMethodParamNames(method);
                Annotation[][] pa = method.getParameterAnnotations();
                for(int i = 0 ; i < paramsTypes.length ; i++) {
                    Class<?> parameterType = paramsTypes[i];
                    if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
                        //paramValues[i] = req;
                        paramIndexMapping.put(parameterType.getName(),i);
                    } else {
                        if (pa[i].length>0) {
                            for (Annotation annotation : pa[i]) {
                                if (annotation instanceof MyRequestParam) {
                                    String paramName = ((MyRequestParam) annotation).value();
                                    if (!"".equals(paramName.trim())) {
                                        //paramValues[i] = convert(parameterType,params.get(paramNames[i]));
                                        paramIndexMapping.put(paramNames[i],i);
                                    } else {
                                        paramIndexMapping.put(paramName,i);
                                    }
                                }
                            }
                        } else {
                            paramIndexMapping.put(paramNames[i],i);
                        }

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
    }

}
